package Entidades;
import TDA.*;
import Implementaciones.*;

public class Tablero {

	MatrizTDA<Barco> tablero;
	int dimensionTablero;
	Vector<Integer> cantOcupadasEnFilas;
	Vector<Integer> cantOcupadasEnColumnas;
	
	/**
	 * 
	 * @param n
	 */
	public Tablero (int n){
		tablero = new Matriz<Barco>();
		this.dimensionTablero = n;
		tablero.inicializarMatriz(n);
		this.cantOcupadasEnColumnas = new Vector<Integer>();
		this.cantOcupadasEnColumnas.inicializarVector(n);
		this.cantOcupadasEnFilas = new Vector<Integer>();
		this.cantOcupadasEnFilas.inicializarVector(n);
	}
	
	/**
	 * 
	 * @param i
	 * @param j
	 * @param b
	 */
	public void setearValor(int i, int j, Barco b){
		tablero.setearValor(i, j, b);
	}
	
	/**
	 * 
	 * @param i
	 * @param j
	 * @return
	 */
	public Barco obtenerValor(int i,int j){
		return tablero.obtenerValor(i, j);
	}
	
	/**
	 * 
	 * @param i
	 * @param cantOcupadaEnF
	 * @throws Exception
	 */
	public void setearValorEnfila(int i, int cantOcupadaEnF) throws Exception{
		if (i < this.dimensionTablero){
		 this.cantOcupadasEnFilas.agregarElemento(i,cantOcupadaEnF);
		}
	}
	
	/**
	 * 
	 * @param j
	 * @param cantOcupadaEnC
	 * @throws Exception
	 */
	public void setearValorEnColumna(int j, int cantOcupadaEnC) throws Exception{
		if (j < this.dimensionTablero){
		 this.cantOcupadasEnColumnas.agregarElemento(j,cantOcupadaEnC);
		}
	}
	
	/**
	 * 
	 * @param i
	 * @return
	 * @throws Exception
	 */
	public int obtenerValorEnfila(int i) throws Exception{
		 return this.cantOcupadasEnFilas.recuperarElemento(i);
	}
	
	/**
	 * 
	 * @param j
	 * @return
	 * @throws Exception
	 */
	public int obtenerValorEnColumna(int j) throws Exception{
		 return this.cantOcupadasEnColumnas.recuperarElemento(j);
	}
	
	/**
	 * 
	 * @return
	 */
	public int obtenerDimensionTablero(){
		return this.dimensionTablero;
	
	}
	
	/** O(n)
	 * Pone un barco en el tablero en la posicion fila i, columna j
	 * @param i fila donde se debe ubicar el barco
	 * @param j columna donde se debe ubicar el barco
	 * @param elBarco 
	 * @return
	 */
	public boolean setearBarco(int i, int j, Barco elBarco){
		// las coordenadas estan dentro del tablero
		if(this.obtenerValor(i, j) == null){
			if(elBarco.obtenerOrientacion() == Orientacion.Horizontal){
				
				// el barco entra
				if(j + elBarco.obtenerCantidadCeldas() <= tablero.obtenerDimension()){
					
					// el barco no se pone encima de otro barco
					for(int f = 0 ; f < elBarco.cantidadCeldas ; f++){
						if(this.obtenerValor(i, j + f) != null)
							return false;
					}
					
					// paso las validaciones, poner el barco en el tablero
					for(int jj = 0 ; jj < elBarco.cantidadCeldas ; jj++)
						this.setearValor(i, j + jj, elBarco);
					return true;
				}
			}
			// Lo mismo que en el otro, pero ahora para los verticales viendo que entre en la posicion...
			else{
				if(i + elBarco.obtenerCantidadCeldas() <= tablero.obtenerDimension()){
					for(int ii = 0 ; ii < elBarco.cantidadCeldas ; ii++){
						if(this.obtenerValor(i + ii, j) != null)
							return false;
					}
					for(int ii = 0 ; ii < elBarco.cantidadCeldas ; ii++)
						this.setearValor( i + ii , j, elBarco);
					return true;
				}
			}
		}
		// si llego aca es porque la posicion en el tablero no existe...
		return false;
	}
	
	/**
	 * Muestra el tablero. La idea es que lo muestre cuando tenga uno que es valido.
	 */
	public void imprimirTablero(){
		System.out.println(" ");
		for(int fila = 0;fila<dimensionTablero;fila++){
			try {
				System.out.print(this.obtenerValorEnfila(fila) + "   ");
			} catch (Exception e) {
				e.printStackTrace();
			}
			for(int col = 0;col<dimensionTablero;col++){
				Barco b = tablero.obtenerValor(fila, col);
				if(b == null)
					System.out.print(" ~~");
				else
					System.out.print(" " + b.obtenerNombre());
			}
			System.out.print("\n");
		}
		System.out.println(" ");
		System.out.print("     ");
		for(int i = 0 ; i < this.dimensionTablero ; i++)
			try {
				System.out.print(this.obtenerValorEnColumna(i) + "  ");
			} catch (Exception e) {
				e.printStackTrace();
			}
		System.out.print("\n\n");		
	}
	
	/**
	 * O(n3)
	 * Verifica que los barcos no esten pegados
	 * @return
	 */
	public boolean validarPosiciones(){
		for(int fila = 0; fila < dimensionTablero; fila++){
			for(int col = 0; col < dimensionTablero; col++){
				Barco unBarco = this.obtenerValor(fila, col);				
				if(unBarco != null){
					if (!this.chequearContiguas(unBarco, fila, col)){
						return false;
					}
				}
			}
		}
		return true;
	}
	
	/** O(n^2)
	 * Devuelve true si el valor de la suma en las filas y columnas coincide con lo especificado en las celdas de suma. 
	 * @return
	 */
	private boolean validarSuma(){
		try {
			int[] sumaFila = new int[this.dimensionTablero];
			int[] sumaCol = new int[this.dimensionTablero];
			
			// suma de los valores segun haya o no barcos
			for(int fila = 0; fila < dimensionTablero; fila++){
				for(int col = 0; col < dimensionTablero; col++){
					Barco unBarco = this.obtenerValor(fila, col);				
					if(unBarco != null){
						sumaFila[fila]++;
						sumaCol[col]++;
					}
				}
			}
			
			// comparacion de los valores obtenidos antes con las sumas
			for(int i = 0 ; i<this.dimensionTablero ; i++){
					if(this.obtenerValorEnfila(i) != sumaFila[i] || this.obtenerValorEnColumna(i) != sumaCol[i])
						return false;
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			return false; // si no puedo completar las comparaciones la funcion no es valida.
		}
		return true;
	}
	
	/** O(n^2)
	 * Valida que la suma de filas y columnas sea la solicitada, y 	que los barcos no esten pegados. 
	 * @return
	 */
	public boolean Validar(){
		if(this.validarSuma() && this.validarPosiciones()) // O(n^2) y O(n)
			return true;
		return false;
	}

	/** O(n)
	 * Valida que no haya otro barco en las celdas contiguas
	 * @param b
	 * @param f
	 * @param c
	 * @return
	 */
	public boolean chequearContiguas(Barco elBarco, int f, int c) {
		
		Barco unBarco;
		
		if (c-1 >= 0) {
			unBarco = this.obtenerValor(f, c-1);
			if (unBarco != null && !(unBarco.obtenerNombre().equals(elBarco.obtenerNombre()))) {
				return false;
			}
		}
		if (f-1 >= 0) {
			unBarco = this.obtenerValor(f - 1, c);
			if (unBarco != null && !(unBarco.obtenerNombre().equals(elBarco.obtenerNombre()))) {
				return false;
			}
		}
		if (c+1 < this.dimensionTablero) {
			unBarco = this.obtenerValor(f, c+1);
			if (unBarco != null && !(unBarco.obtenerNombre().equals(elBarco.obtenerNombre()))) {
				return false;
			}
		}
		if (f+1 < this.dimensionTablero) {
			unBarco = this.obtenerValor(f+1, c);
			if (unBarco != null && !(unBarco.obtenerNombre().equals(elBarco.obtenerNombre()))) {
				return false;
			}
		}

		/*
		for(int fila = -1; fila < 2; fila++){
			for(int col = -1; col < 2; col++){

				if(f + fila >= 0 && f + fila < dimensionTablero){
					if(c + col >= 0 && c+col < dimensionTablero){
						unBarco = this.obtenerValor( f + fila, c + col);
						if(unBarco != null && !unBarco.obtenerNombre().equals(unBarco.obtenerNombre())){
								return false;
						}
					}
				}
			}
		}*/
		return true;
	}

	/** 
	 * O(n)
	 * Saca el barco del tablero
	 * @param fila
	 * @param col
	 * @param b
	 */
	public void quitarBarco(int fila, int col, Barco elBarco) {
		for(int i = 0 ; i < elBarco.obtenerCantidadCeldas() ; i++){
			if (elBarco.obtenerOrientacion() == Orientacion.Horizontal)
				tablero.setearValor(fila, col + i, null);
			else
				tablero.setearValor(fila + i, col, null);				
		}
	}
}
